Java Basics

Must-know-things for Java Developers.

Java环境变量配置

Windows

  1. 安装
    安装JDK(Java Development Kit),JDK自带JRE(Java Runtime Environment)
  2. 配置环境变量
    右键计算机->属性->高级->环境变量->系统变量
    • (可选)添加变量JAVA_HOME=[JDK的根目录],e.g. E:\java\jdk1.8
    • (可选)添加变量CLASSPATH=%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar
    • 若添加了JAVA_HOME变量,在Path变量中追加:
      ”.;%JAVA_HOME%\bin;%JAVA_HOME%\jre\bin;”
    • 若未添加JAVA_HOME变量,在Path变量中追加:
      ”.;E:java\jdk1.8\bin;E:java\jdk1.8\jre\bin;”
  3. 重启

Linux

以Ubuntu安装Oracle Java 8为例

  1. 安装软件库管理工具

    1
    apt-get install software-properties-common
  2. 添加软件库

    1
    2
    add-apt-repository ppa:webupd8team/java
    apt-get update
  3. 安装Java

    1
    apt-get install oracle-java8-installer
  4. 验证安装

    1
    java -version
  5. 选择active的Java

    1
    sudo update-alternatives --config java

    同样可以选择active的javac

  6. 设置环境变量

    1
    nano /etc/environment
    • 添加JAVA_HOME=”[path]”
      e.g. /usr/lib/jvm/java-8-oracle
    • PATH变量中追加:$JAVA_HOME/bin
    • 添加CLASSPATH=”.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar”
  7. 更新环境变量

    1
    source /etc/environment
  8. 验证环境变量

    1
    2
    echo $JAVA_HOME
    javac

OS X

  1. 安装
    安装JDK(Java Development Kit),JDK自带JRE(Java Runtime Environment)
  2. 验证安装
    1
    2
    java -version
    javac

JDK文件构成

一个完整的JDK主要包含以下目录。

  • bin目录
    • 编译器(javac)
    • 解释器(java)
    • 调试器(debugger)
    • 文档生成器(javadoc)
  • demo目录
    • 一些代码的演示
  • include目录
    • 用于编译本地方法的文件,如JNI调用时所需的C语言头文件
  • jre目录
    • JVM运行环境,JDK类库,与所在平台相关
  • lib目录
    • 除jre目录下之外JDK额外所需的类库
  • src.zip
    • JDK源代码

Java平台体系

  • J2SE
    Java 2 Standard Edition,标准版(基础)
  • J2EE
    Java 2 Enterprise Edition,企业版(Web应用)(基于J2SE)
  • J2ME
    Java 2 Mobile Edition,移动版(移动设备应用)(基于J2SE)

Java特性

  • OO
    面向对象。以对象为基本粒度,对象的属性定义了这个对象,对象的方法可以用来操作对象。这使得代码比较human-being-like,且利于代码扩展和复用。
  • Platform-independent
    硬件平台的数量较多,但是要写的软件较之近乎无限。Java源代码编译后的字节码与平台无关,与底层硬件打交道的事情交给了JVM,真正做到了Write/Compile once, run everywhere.
  • Safe
    包括语言级安全性(对象的封装),编译时安全性(语法语义检查),运行时安全性(类装载器装载及字节码校验器校验)和可执行代码安全性(网络访问类时进行了权限设置)。
    当然,总有安全漏洞躺在人们意想不到的地方。
  • Threading
    多线程使得程序对硬件的利用率极大地提高了。

Java程序生命周期

  1. 编译
    源文件(.java)–javac(java compiler)–>字节码(byte code)(.class)
    • JVM只能运行字节码文件
  2. 装载
    字节码(byte code)(.class)–JVM类装载器(Class Loader)–>JVM中装载的类
  3. 校验
    JVM中装载的类–>JVM字节码校验器–>进行校验
  4. 解释
    JVM中装载的类–JVM解释器(根据OS解释)–>机器码
  5. 运行
    机器码–>OS–>运行的程序
  6. 垃圾回收
    内存垃圾–>JVM垃圾回收器–>垃圾回收

    • 垃圾
      当对象失去所有的引用,变成了孤魂野鬼,也就是“垃圾”。
      下面是垃圾对象的诞生过程之一:

      1
      2
      3
      4
      String str1 = new String("abc"); //这个对象有一个引用str1指向它
      String str2 = str1; //这个对象现在有两个引用:str1,str2指向它
      str1 = null; //失去引用str1
      str2 = null; //失去引用str2,失去全部引用,变成野对象

      就这样,由于new String(“abc”);产生的对象失去了所有引用,将无法再被程序访问。
      在C/C++中,没有垃圾自动回收机制,因此除非程序结束,堆中申请后未被释放且无指向的内存将一直处于三无状态:无法释放,无法调用,无法更改,因为你根本没有办法找到它。当此类内存多起来时,可以利用的内存将变得越来越少,这就是内存泄露。
      如下是一次内存泄露的发生过程:

      1
      2
      (int *) malloc (sizeof(int)*1024);
      //内存分配函数的参数没有问题,指针的转型也没有问题,But没有任何指针指向它!

      在Java中,由于有了垃圾回收机制,不定期地收回垃圾对象所占的内存,内存泄露的隐患将大大降低。

  7. 终止
    JVM卸载程序

注释风格 & 命名规则

  • 单行注释

    1
    // 这是一个单行注释
  • 多行注释

    1
    2
    3
    4
    /*
    大家好,
    我是多行注释
    */

  • 类、方法注释

    1
    2
    3
    4
    /**
    * 文档/类/方法的注释
    *
    */

  • packages命名
    com.公司名.项目名.模块名
    e.g. com.tencent.qq.view

  • 变量/方法命名
    1. 不与关键字冲突
    2. 不以数字开头
    3. 可含$、_,此外不能有其他符号
    4. 可用$、_开头
    5. 伪驼峰命名:userName,myAccount
  • 常量命名
    全大写,final修饰,常配以static修饰符,e.g.
    1
    public static final double PI = 3.14159;
  • 项目/类/接口命名
    1. 基本与变量/方法名相同
    2. 驼峰命名:MyClass,MyInterface

访问权限

  • 文字版
    • private
      不允许同包子类、不允许同包其他类、不允许跨包子类、不允许跨包其他类访问
    • 默认
      允许同包子类、允许同包其他类、不允许跨包子类、不允许跨包其他类访问
    • protected
      允许同包子类、允许同包其他类、允许跨包子类、不允许跨包其他类访问
    • public
      全允许
  • 表格版
同一个类 同包其他类 不同包的子类 不同包的其他类
private
默认
protected
public
  • 推导规则
    • 继承时,子类的重写不可以缩小权限,父类方法修饰符为private时对于子类不可见
    • 父类方法无修饰符时,子类重写方法可选默认、protected、public
    • 父类方法修饰符为protected时,子类重写方法可选修饰符为protected或public
    • 父类方法修饰符为public时,子类重写方法只能修饰为public

基本类型

类型 byte char short int float long double boolean
长度 1 2 2 4 4 8 8 int/byte
  • 单个boolean变量的长度等于int的长度,boolean数组的长度等于byte数组
  • 小数默认为double型,显式声明f或F才为float
  • 整数默认为int型,显式声明为l或L才为long

    1
    2
    float f = 1.0f;
    long l = 1l;
  • 零值比较: byte, char, short, int, long, boolean都可以直接对比,对于float和double,由于只能趋近于零,需要设定精度:

    1
    2
    3
    final float EPISNON = 0.0001f;
    f >= -EPISNON && f <= EPISNON
    Math.abs(f) < EPISNON
  • 数组是一个对象,不属于基本类型

  • true/false/null均不是关键字,而是字面量(literal),同数字

实例化

  • 构造方法:
    1. 如果没有定义构造方法,JVM将为类生成一个默认的无参构造方法;如果定义了,将不会自动生成默认无参构造方法
    2. 如果父类未定义构造方法或定义了无参构造方法,则子类所有构造方法隐式调用该方法;若其只定义了带参构造方法,则父类没有无参构造方法,子类所有构造方法需显式调用父类任意一个带参构造方法
  • 构造顺序
    1. 父类静态块及静态成员对象初始化(按顺序执行,仅在第一次加载时进行)
    2. 子类静态块及静态成员对象初始化(按顺序执行,仅在第一次加载时进行)
    3. 父类成员对象初始化
    4. 父类构造块
    5. 父类构造方法
    6. 子类成员对象初始化
    7. 子类构造块
    8. 子类构造方法
    9. 子类内部类
  • 构造块 vs 静态块

    1. 静态块只执行一次
    2. 构造块在每次构造时执行
      1
      2
      3
      4
      5
      6
      {
      //Construction block
      }
      static {
      //Static block
      }
  • 拷贝一个对象

  1. 对象的类实现了Cloneable接口(空接口)
  2. 重写Object类的clone方法
  3. clone方法中注意深拷贝

异常

当程序的运行出现逻辑错误,或出现无效的情况时,即为异常。
Java定义的异常全部基于Throwable,继承于Throwable的两个子类Error和Exception,其中Error仅在发生JVM级的错误时才会抛出,一般程序不处理。

Unchecked Exception

  1. 表示错误,即程序的问题或逻辑错误,运行时无法恢复,不需要抛出异常。
  2. 包括Error及RuntimeException及其子类,e.g. OutOfMemoryError、IllegalArgumentException、NullPointerException、IllegalStatementException、ClassCastException、IndexOutOfBoundException、ArithmeticException。

Checked Exception

  1. 表示无效,即不是程序可以预测的异常,比如无效的用户输入、文件不存在等,必须显示地捕获处理或向外抛出。
  2. 包括Exception除RuntimeException之外的所有子类,e.g. FileNotFoundException、SocketException、SQLException、IOException。

异常处理

通常我们要处理Checked Exception,可以使用throws关键字或try-catch块进行处理。

throws

如果要监控整个方法中的某一类异常,且将其往外抛出,使用throws关键字。
这样当方法return前任何一句代码发生对应异常,或使用throw关键字手动抛出时,异常都会向外抛出,交由外层调用方法进行处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
public void divide(int a, int b) throws Exception {
int c;
if(0 == b){
// 抛给method法的调用者
throw new Exception("divided by 0");
}
c = a/b;
}
public static void main(Stringp[] args) throws Exception {
int result = divide(1,0);
System.out.println(result);
//如果不作处理,异常层层外抛,最终交由JVM处理
}

try-catch-finally

如果需要对某一块代码中的代码进行监控,且有意对异常在方法内进行处理时,使用try-catch块。当然,在try-catch块中,仍然可以配合throw/throws将异常外抛。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public void divide(int a, int b) throws Exception {
int c;
if(0 == b){
// 抛给method法的调用者
throw new Exception("divided by 0");
}
c = a/b;
}
public static void main(Stringp[] args) {
try{
int result = divide(1,0);
} catch (Exception e) {
System.out.println("error, cause: " + e.getMessage());
}
System.out.println(result);
}

关于finally,多说几句,有如下几个规则:

  1. finally是可选子句,有try子句必有catch子句,但不一定有finally子句。
  2. finally子句中的内容一定会执行,除非在try或者catch中含有JVM终止的语句[System.exit(0);]。
  3. finally子句在try或者catch子句里的return语句执行之后(如果return紧接的是一个运算或者方法调用),return返回之前(方法出栈)。
  4. 如果finally子句和try或catch语句中都有return语句,真正返回的是finally中的return语句。

版本差异

Java 7

Java 7推出于2011年,是Java史上最大的一次更新,其中有许多developer应当知道的新特性。

  1. 二进制表示基本类型变量

    1
    2
    byte b = (byte)0b00000001;
    int i = 0b00000001;
  2. 下划线分隔数字

    1
    long aLongDecimal = 1234_5678_9012L;
  3. switch字符串变量

    1
    2
    3
    4
    5
    6
    7
    8
    9
    switch(dayName) {
    case "Monday": recover();
    case "Tuesday": work(hard);
    case "Wednesday": work(notThatHard);
    case "Thursday": work(lazily);
    case "Friday": work(withoutPatience);
    case "Saturday": party(hard);
    case "Sunday": party(hard);
    }
  4. 泛型实例化自动推断类型

    1
    ArrayList<String> sList = new ArrayList<>();
  5. try-with-resource
    使用try-with-resource子句后,不再需要手动关闭资源

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    //java 6-
    try {
    FileReader fr = new FileReader(path)
    BufferedReader br = new BufferedReader(fr);
    return br.readLine();
    } catch (Exception e) {
    throw e;
    } finally {
    if (br != null) br.close();
    if (fr != null) fr.close();
    }

    //java 7+
    //try-with-resource can make sure the resources are closed before returning
    try (
    FileReader fr = new FileReader(path)
    BufferedReader br = new BufferedReader(fr);
    ) {
    return br.readLine();
    } catch (Exception e) {
    throw e;
    }
  6. 单个catch字句捕获多个异常

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    //java 6-
    try{
    ..//block in which exceptions to be caught
    } catch (NoSuchFileException e1) {
    logger.log(e1);
    throw new Exception(e1);
    } catch (IOException e2) {
    logger.log(e2);
    throw new Exception(e2);
    }

    //java 7+
    try{
    ..//block in which exceptions to be caught
    } catch(NoSuchFileException | IOException ex){
    logger.log(ex);
    throw new Exception(ex);
    }

Java 8

发布于2014年的Java 8有重要更新,需要developer重点关注。

  1. 移除位于JVM内存中的PermGen,新增位于本地内存中的Metaspace
  2. 接口扩展, 给接口方法添加默认或静态实现(default关键字)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    //默认实现
    interface Example{
    default int add(int a, int b){
    return a+b;
    }
    }

    //静态方法
    interface Example{
    static int add(int a, int b){
    return a+b;
    }
    }
  3. 函数式接口
    任何接口,如果只含有一个抽象方法,就是一个函数式接口(添加@FunctionalInterface注解)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    		//Function<T, R>,输入T, 输出R   
    Function<String,String> function = (x) -> {
    System.out.print(x+": ");
    return "Function";
    };
    System.out.println(function.apply("hello world"));

    //Predicate<T> -输入T, 返回一个boolean值
    Predicate<String> pre = (x) ->{
    System.out.print(x);
    return false;
    };
    System.out.println(": "+pre.test("hello World"));

    //Consumer<T> - 输入T, 执行动作无输出
    Consumer<String> con = (x) -> {
    System.out.println(x);
    };
    con.accept("hello world");

    //Supplier<T> - 无输入, 输出T
    Supplier<String> supp = () -> {
    return "Supplier";
    };
    System.out.println(supp.get());

    //BinaryOperator<T> - 输入两个T,输出T,think about reduce of map-reduce
    BinaryOperator<String> bin = (x,y) ->{
    System.out.print(x+" "+y);
    return "BinaryOperator";
    };
    System.out.println(" "+bin.apply("hello ","world"));


    output:
    hello world: Function
    hello World: false
    hello world
    Supplier
    hello world BinaryOperator
  4. 方法引用

    • Reference to constructor, 构造方法引用
    • Reference to static method, 类静态方法引用
    • Reference to methods of an instance, 某个对象的方法引用
    • Reference to methods of a specific class for any instance, 特定类的任意对象的方法引用

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      91
      92
      93
      //首先定义一个基本的entity类Person, nothing special
      class Person {
      private String name;
      public Person() {}
      public String getName() {
      return name;
      }
      public void setName(String name) {
      this.name = name;
      }
      public int compare(Person a, Person b) {
      return a.getName().compareTo(b.getName());
      }
      public int compareTo(Person p) {
      return this.getName().compareTo(p.getName());
      }
      }

      //再定义一个PersonFactory,注意此处有一个java.util.function.Supplier,无输入,单个输出。

      ```java
      class PersonFactory {
      private Supplier<Person> supplier;
      public PersonFactory(Supplier<Person> supplier) {
      this.supplier = supplier;
      }
      public Person getPerson() {
      return supplier.get();
      }
      }


      //最后是测试类

      ```java
      public class MethodReference {

      public static void main(String[] args) {
      // 引用构造函数
      PersonFactory factory = new PersonFactory(Person::new);

      List<Person> personList = new ArrayList<Person>();
      Person p1 = factory.getPerson();
      p1.setName("Kobe");
      personList.add(p1);
      Person p2 = factory.getPerson();
      p2.setName("James");
      personList.add(p2);
      Person p3 = factory.getPerson();
      p3.setName("Paul");
      personList.add(p3);

      Person[] persons1 = personList.toArray(new Person[personList.size()]);
      System.out.print("before: ");
      printArray(persons1);

      // 引用静态方法
      Arrays.sort(persons1, MethodReference::myCompare);
      System.out.print("after: ");
      printArray(persons1);
      System.out.println();

      Person[] persons2 = personList.toArray(new Person[personList.size()]);
      System.out.print("before: ");
      printArray(persons2);

      // 引用特定对象的实例方法
      Arrays.sort(persons2, p1::compare);
      System.out.print("after: ");
      printArray(persons2);
      System.out.println();

      Person[] persons3 = personList.toArray(new Person[personList.size()]);
      System.out.print("before: ");
      printArray(persons3);

      // 引用特定类型的任意对象的实例方法
      Arrays.sort(persons3, Person::compareTo);
      System.out.print("after: ");
      printArray(persons3);
      }

      public static void printArray(Person[] persons) {
      for (Person p : persons) {
      System.out.print(p.getName() + " ");
      }
      System.out.println();
      }

      public static int myCompare(Person p1, Person p2) {
      return p1.getName().compareTo(p2.getName());
      }
      }
  5. Lambda Expression

    • a.k.a clousure
    • 把函数当成方法参数,可以替代匿名对象
    • e.g. 数组foreach

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      List<String> names = Arrays.asList("b", "c", "a", "d");
      String sep = ",";
      //基本
      names.forEach( e -> {
      System.out.println(e + sep); //sep变为final
      });

      //简化, 指定迭代类型,
      names.forEach( ( String e ) -> System.out.println( e ) );

      //简化, 类型自动推断
      names.forEach( e -> System.out.println( e ) );
    • e.g. 字符串数组排序

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      List<String> names = Arrays.asList("b", "c", "a", "d");
      //传统匿名对象
      Collections.sort(names, new Comparator<String>() {
      @Override
      public int compare(String a, String b) {
      return a.compareTo(b);
      }
      });

      //普通lambda
      Collections.sort(names, (String a, String b)->{
      return a.compareTo(b);
      });

      //简化只有一句的方法
      Collections.sort(names, (String a, String b)->a.compareTo(b));

      //进一步简化,类型自动推断
      Collections.sort(names, (a, b)->a.compareTo(b));